#include <stdbool.h>
#include <stdio.h>

#include <board.h>
#include <corepac.h>
#include <emif.h>
#include "mcbl.h"
#include "memspace.h"

#if 0
#define msg printf
#else
static inline void msg(const char* fmt, ...) { return; }
#endif

typedef enum {
    preamble = ':',
    separator = '\n',
    dataType = '0',
    eofType = '1',
    rebaseType = '4'
} ihexConst;


#define BYTE_HEX_SZ     2
#define DB_BYTE_SZ      2
typedef uint8_t Hex;
typedef struct {
    Hex preamble;
    Hex size[BYTE_HEX_SZ];
    Hex offset[DB_BYTE_SZ * BYTE_HEX_SZ];
    Hex type[BYTE_HEX_SZ];
    Hex payload[IHEX_SZ_MAX * DB_BYTE_SZ];
    Hex checksum[BYTE_HEX_SZ];
    Hex separator;
    Hex rsvd[8];
} IHexRecord;

#define hex2b(hex)      ((hex <= '9') ? (hex & 0xf) : (hex - 'A' + 10))

static __inline uint8_t hex2byte(const Hex* hex) {
    register uint8_t h = *hex++;
    register uint8_t b = hex2b(h) << 4;
    h = *hex;
    b |= hex2b(h);
    return b;
}

static __inline
uint16_t
hex2db(
	register const Hex* hex
){
    register uint16_t db = hex2byte(hex) << 8;
    db |= hex2byte(hex + BYTE_HEX_SZ);
    return db;
}

static __inline
void
hex2nBytes(
	register const Hex* hex,
	register int16_t n,
	register uint8_t* bytes
){
    while(--n >= 0) {
        *bytes++ = hex2byte(hex);
        hex += BYTE_HEX_SZ;
    }
}


void errHang(void) {
    printf("error occurs, curing progress hung, fix & try again!\n");
    do {;} while(true);
}


#pragma WEAK(flashErase)
int flashErase(void* from, int size){
    msg("\t\te%x+%x\n", from, size);

    uint32_t offset = (uint32_t)from - NOR_BASE;
    return Nor_erase(offset, (uint32_t)size);
}
#pragma WEAK(flashWrite)
void flashWrite(uint32_t to, void* from, int size) {
    msg("\t\tw%x + %x -> %x\n", from, size, to);

    uint32_t offset = (uint32_t)to - NOR_BASE;
    (void) Nor_write(offset, from, (uint32_t)size);
}

void curingBootloader(const char* fbl, Write write_) {
    printf("curing bootloader with %s.\n", fbl);

    FILE* ifp = fopen(fbl, "r");
    if (NULL == ifp) {
        perror(NULL);
        printf("\tfile %s not exist!\n", fbl);
        errHang();
    }

    IHexRecord  ihex = {0};
    uint32_t    rbase = 0;
    uint16_t    roff;
    int8_t      rsz;
    char        rt;

    printf("\t");fflush(stdout);

    uint8_t dummy[IHEX_SZ_MAX * 2];
    do {
        (void)fgets((char*)&ihex, sizeof(IHexRecord), ifp);
        if (preamble != ihex.preamble) {
            printf("invalid preamble %x-%c in file.\n",
            		ihex.preamble, ihex.preamble);
            errHang();
        }
        rt = ihex.type[1];
        if (rt == dataType) {
            rsz = hex2byte(&ihex.size[0]);
            if (rsz > IHEX_SZ_MAX) {
                printf("invalid record size: %x.\n", rsz);
                errHang();
            }
            roff = hex2db(&ihex.offset[0]);
            hex2nBytes(&ihex.payload[0], rsz, &dummy[0]);
            write_(rbase + roff, &dummy, rsz);
       } else if(rt == rebaseType) {
            rbase = (uint32_t)hex2db(&ihex.payload[0]) << 16; }

        printf(".");fflush(stdout);
    } while (rt != eofType);
    printf("\n\t finished!\n\n");

    fclose(ifp);
}

uint32_t ihex2ibin(CorePac core, const char* fimg, Write write_, uint32_t ct) {
    printf("curing core-%x with %s to %x.\n", core, fimg, ct);

    FILE* ifp = fopen(fimg, "r");
    if (NULL == ifp) {
        perror(NULL);
        printf("\t file %s not exist!\n", fimg);
        errHang();
    }

    Btbl        ibl = {0};
    IHexRecord  ihex;
    uint32_t    isz = 0;
    uint32_t    entry = (uint32_t)INVALID;
    uint32_t    len = (uint32_t)INVALID;
    uint32_t    to = (uint32_t)INVALID;
    int8_t      rsz;
    char        rt;

    Btx* dbtx = (Btx*)(ct + sizeof(ibl.bt0.head.entry));

    do {//each record line
        (void)fgets((char*)&ihex, sizeof(IHexRecord), ifp);
        if (preamble != ihex.preamble) {
            printf("invalid preamble %x-%c in file.\n", rt, rt);
            errHang();
        }


        rt = ihex.type[1];
        if (eofType == rt) {
            break; }
        if (rebaseType == rt) {
            continue; }
        else if (dataType != rt) {
            printf("invalid data type %x-%c.\n", rt, rt);
            errHang();
        }

        rsz = hex2byte(&ihex.size[0]);
        if (rsz > IHEX_SZ_MAX) {
            printf("invalid record size: %x.\n", rsz);
            errHang();
        } else if (0 == rsz) {
            printf("\t end of image.\n");
            break; }

        //share used.
        Btx* sbtx = (Btx*)&ibl.bt0.head.length;

        if (entry == (uint32_t)INVALID) {//first section
            hex2nBytes(&ihex.payload[0], rsz, (uint8_t*)&ibl);
            entry = ibl.bt0.head.entry;
            len = sbtx->head.length;
            to = sbtx->head.to;
            rsz -= sizeof(ibl.bt0.head);

            printf("\t -entry @%08x\n"
                   "\t section @%08x + %x, run @%08x.\n\t",
                    entry, dbtx, len, to);
        } else if (to == (uint32_t)INVALID) {//new section
            hex2nBytes(&ihex.payload[0], rsz, (uint8_t*)sbtx);
            len = sbtx->head.length;
            rsz = len ? rsz - sizeof(sbtx->head) : 0;
            dbtx->head.to = to = len ? sbtx->head.to : (uint32_t)INVALID;


            printf("\t section @%08x + %x, run @%08x.%s\n\t ",
                    dbtx, len, to, len ? "" : "- image parse finished!");
        } else if (isz != 0) {//section continue
            hex2nBytes(&ihex.payload[0], rsz, (uint8_t*)&sbtx->payload);

            if (((isz & 0xFFF) == 0x14) || ((isz & 0xFFF) == 0x18)) {//4KB mark.
                printf(".");fflush(stdout); }
        }

        //write pay-load
        write_((uint32_t)&dbtx->payload[isz], (uint8_t*)&sbtx->payload, rsz);
        isz += rsz;

        if ((isz >= len) || (len == 0)) {//end of a section, write it's head.
            sbtx->head.length = (len == 0) ? (unsigned)INVALID : isz;
            write_((uint32_t)&dbtx->head, &sbtx->head, sizeof(sbtx->head));
            dbtx = (Btx*)&dbtx->payload[isz];

            printf("\n\t\t - finally length - %x, word %saligned, next @%08x.\n\n",
                   isz, (isz & 3) ? "!!UN-" : "", dbtx);
            if (isz & 3) errHang();

            to = (uint32_t)INVALID;
            isz = 0;
        }

    } while (NONE != len);

    printf("\t nearly finished, write entry.\n");
    write_(ct, &ibl.bt0.head.entry, sizeof(entry));
    ct = (uint32_t)&dbtx->payload[isz + sizeof(dbtx->head)];
    fclose(ifp);

    printf("Succed in curing %s!, next curing @ %08x.\n\n", fimg, ct);

    return ct;
}


void curing(McblCfg* cfg) {
    if (cfg->curingBootloader) {
        printf("erasing flash for bootloader @%x + %x...\n",
               BOOT_BASE, cfg->bootloaderSz);
        flashErase((void*)BOOT_BASE, cfg->bootloaderSz);
        curingBootloader(&cfg->image[cfg->coreCnt + 1][0], flashWrite);
    }

    if (cfg->curingCoreApp) {
        printf("\nerasing flash for core's application  @%x + %x...\n\n",
               MCBL_TLB_BASE, cfg->uimgTotalSz);
        flashErase((void*)MCBL_TLB_BASE, cfg->uimgTotalSz);

        short cores = cfg->coreCnt;
        McblImgDesc des = {
           .CORES = cores,
           .SMPM = cfg->smpMask,
        };

//         .UDATE = TODO copy current DATE & TIME to

        uint32_t curingMask = cfg->curingCoreApp;
        uint32_t ct = MCBL_ROUND_UP(UIMG_BASE);//curing to

        //TODO smp not supported right now.
        if (des.SMPM) {
            printf("SMP mode not supported right now!\n");
            return;
        } else {
            curingMask &= ~(1<<cores); }


        for (short core = COREPAC0; core <= cores; core++) {//<= to
            if (NONE == (curingMask & (FLG << core))) {
                des.BTBLT[core] = (Btbl*)INVALID;
                continue;
            }

            CSL_FINS(ct, MCBL_ENTRY_VALID, MCBL_ENTRY_VALID);
            des.BTBLT[core] = (Btbl*)ct;
            ct = ihex2ibin((CorePac)core, &cfg->image[core][0], flashWrite,
                           ct & MCBL_ENTRY_VAL_MASK);//-1 to remove VALID flg
            ct = MCBL_ROUND_UP(ct);
        }

        flashWrite(MCBL_TLB_BASE, &des, sizeof(des));
    }
}

